package srf

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"

	"github.com/iancoleman/strcase"
	"github.com/rs/cors"
)

type Server struct {
	controllerRegistry          map[string]interface{}
	registeredPathAndController map[string]map[string]map[string]string
}

type Action func(request *http.Request) (statusCode int, response interface{}, responseHeader http.Header)

func (s *Server) RegisterControllers(controllers []interface{}) {
	for _, v := range controllers {
		s.RegisterController(v)
	}
}

func (s *Server) RegisterController(controller interface{}) {
	ctrlType := reflect.TypeOf(controller)
	for i := 0; i < ctrlType.NumField(); i++ {
		s.registerAction(ctrlType.Name(), ctrlType.Field(i))
	}
	s.controllerRegistry[ctrlType.Name()] = controller
}

func (s *Server) Start(port int) {
	s.StartWithCors(port, nil)
}

func (s *Server) StartWithCors(port int, corsOptions *cors.Options) {
	mux := http.NewServeMux()
	s.registerHandleFunc(mux)
	if corsOptions == nil {
		corsOptions = &cors.Options{}
	}
	http.ListenAndServe(fmt.Sprintf(":%d", port), cors.New(*corsOptions).Handler(mux))
}

func New() *Server {
	var result = new(Server)
	result.controllerRegistry = make(map[string]interface{})
	result.registeredPathAndController = make(map[string]map[string]map[string]string)
	return result
}

func (s *Server) handleRequest(controllers map[string]map[string]string) http.HandlerFunc {
	return func(rw http.ResponseWriter, request *http.Request) {
		macthedControllers, ok := controllers[request.Method]
		if !ok {
			rw.WriteHeader(http.StatusMethodNotAllowed)
			return
		}

		request.ParseForm()
		in := []reflect.Value{reflect.ValueOf(request)}
		for k, v := range macthedControllers {
			controller := s.controllerRegistry[k]
			returnValues := reflect.ValueOf(controller).FieldByName(v).Call(in)
			statusCode := returnValues[0].Interface().(int)
			response := returnValues[1].Interface()
			responseHeaders := returnValues[2].Interface().(http.Header)
			if responseHeaders == nil {
				responseHeaders = http.Header{}
			}
			s.jsonResponse(rw, statusCode, response, responseHeaders)
		}
	}
}

func (s *Server) registerHandleFunc(mux *http.ServeMux) {
	for k, v := range s.registeredPathAndController {
		mux.HandleFunc(fmt.Sprintf("/%v", k), s.handleRequest(v))
	}
}

func (s *Server) jsonResponse(rw http.ResponseWriter, statusCode int, response interface{}, headers http.Header) {
	for k, v := range headers {
		for _, header := range v {
			rw.Header().Add(k, header)
		}
	}
	rw.Header().Add("Content-Type", "application/json")
	rw.WriteHeader(statusCode)
	rsp, err := json.Marshal(response)
	if err != nil {
		// TODO: logging error
		fmt.Println("JSON err:", err)
	}
	rw.Write(rsp)
}

func (s *Server) registerAction(ctrlName string, actionField reflect.StructField) {
	method := actionField.Tag.Get("method")
	if method == "" {
		method = http.MethodGet
	}
	action := actionField.Name
	path := strcase.ToKebab(action)
	mapping := make(map[string]map[string]string)
	if length := len(s.registeredPathAndController[path]); length > 0 {
		mapping = s.registeredPathAndController[path]
	}
	mapping[method] = map[string]string{ctrlName: action}
	s.registeredPathAndController[path] = mapping
}
